//! 登陆认证相关的路由
use std::{collections::HashMap, sync::Arc};

use crate::{
    error::AppResult,
    extract::{ClientIp, Json},
    security::{LoginUser, Security},
    state::AppState,
};
use ane_system::entity::sys_login_log;
use axum::{
    async_trait,
    extract::{FromRequestParts, Query},
    handler::Handler,
    http::{header, request::Parts, HeaderMap, HeaderValue, StatusCode},
    response::{IntoResponse, Redirect, Response},
    routing::{get, post},
    Extension, Router,
};
use chrono::Days;
use common::utils::{self, current_datetime};
use cookie::Cookie;
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header, Validation};
use loginmanager::{AuthContext, CurrentUser};
use sea_orm::{ActiveModelTrait, DbErr, Set};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use validator::Validate;

#[derive(Debug, Deserialize)]
pub struct JwtConfigBuilder {
    header_prefix: Option<String>,
    query_key: Option<String>,
    query_prefix: Option<String>,
    issuer: Option<String>,
    access_token_expires: u64,
    refresh_token_expires: u64,
    secret: String,
    algorithm: Option<Algorithm>,
}

impl JwtConfigBuilder {
    /// 从配置文件"application.yml"中加载jwt配置
    pub fn from_file() -> Self {
        let s = match std::fs::read_to_string("application.yml") {
            Ok(s) => s,
            _ => {
                println!("fail to load application.yml, start with default config");
                "".to_owned()
            }
        };
        serde_yaml::from_str(s.as_str()).unwrap()
    }

    pub fn builder(self) -> JwtConfig {
        let header_prefix = self
            .header_prefix
            .map(|f| {
                let f = f.trim();
                if f.len() > 0 {
                    format!("{} ", f)
                } else {
                    "".to_owned()
                }
            })
            .unwrap_or("Bearer ".to_owned());

        let query_prefix = self
            .query_prefix
            .map(|f| {
                let f = f.trim();
                if f.len() > 0 {
                    format!("{} ", f)
                } else {
                    "".to_owned()
                }
            })
            .unwrap_or("".to_owned());

        let algorithm = self.algorithm.unwrap_or(Algorithm::default());
        let mut validation = Validation::new(algorithm);
        if let Some(issuer) = self.issuer {
            validation.set_issuer(&issuer.split(",").collect::<Vec<_>>())
        }

        JwtConfig {
            header_prefix,
            query_key: self.query_key.unwrap_or("access_token".to_owned()),
            query_prefix,
            access_token_expires: self.access_token_expires,
            refresh_token_expires: self.refresh_token_expires,
            secret: self.secret,
            algorithm,
            validation,
        }
    }
}

#[derive(Debug, Clone)]
pub struct JwtConfig {
    header_prefix: String,
    query_key: String,
    query_prefix: String,
    access_token_expires: u64,
    refresh_token_expires: u64,
    secret: String,
    algorithm: Algorithm,
    validation: Validation,
}

impl JwtConfig {
    pub fn encode<T: Serialize>(&self, value: &T) -> Result<String, String> {
        let mut value = serde_json::to_value(value).unwrap();
        let timestamp = jsonwebtoken::get_current_timestamp();
        if let Some(value) = value.as_object_mut() {
            value.insert("iat".to_owned(), timestamp.into());
            value.insert(
                "exp".to_owned(),
                (timestamp + self.access_token_expires).into(),
            );
        }
        encode::<serde_json::Value>(
            &Header::new(self.algorithm),
            &value,
            &EncodingKey::from_secret(self.secret.as_ref()),
        )
        .map_err(|e| e.to_string())
    }

    pub fn decode<T: DeserializeOwned>(&self, token: &str) -> Result<T, String> {
        jsonwebtoken::decode::<T>(
            token,
            &jsonwebtoken::DecodingKey::from_secret(self.secret.as_ref()),
            &self.validation,
        )
        .map(|f| f.claims)
        .map_err(|e| e.to_string())
    }

    /// 从request的[Parts]中读取token str,
    /// 先读取Authorization头，在读取query
    /// token 需要有前缀 默认:Bearer
    pub fn get_token(&self, parts: &Parts) -> Option<String> {
        let token = if let Some(auth) = parts
            .headers
            .get(header::AUTHORIZATION)
            .map(|f| f.to_str().ok())
        {
            auth.map(ToOwned::to_owned)
        } else {
            None
        };
        if token.is_none() {
            let query = parts.uri.query().unwrap_or_default();
            let value: HashMap<String, String> = serde_urlencoded::from_str(query).ok()?;
            value.get(&self.query_key).map(|token| {
                if token.starts_with(&self.query_prefix) {
                    Some(token[self.query_prefix.len()..].to_owned())
                } else {
                    None
                }
            })?
        } else {
            token.map(|token| {
                if token.starts_with(&self.header_prefix) {
                    Some(token[self.header_prefix.len()..].to_owned())
                } else {
                    None
                }
            })?
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: u64,
    // company: String,
    // iat: u64,
    // exp: u64,
}

#[async_trait]
impl<S> FromRequestParts<S> for Claims
where
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let config = parts.extensions.get::<JwtConfig>();
        if let Some(config) = config {
            if let Some(token) = config.get_token(parts) {
                config
                    .decode::<Claims>(&token)
                    .map_err(|e| (StatusCode::UNAUTHORIZED, format!("error {}", e)).into_response())
            } else {
                Err((StatusCode::UNAUTHORIZED, "miss: AUTHORIZATION").into_response())
            }
        } else {
            Err((
                StatusCode::INTERNAL_SERVER_ERROR,
                "please set JwtConfig extensions first",
            )
                .into_response())
        }
    }
}

fn default_db_error(e: DbErr) -> Response {
    (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
}

/// 认证相关的路由
/// - {prefix}/login POST登陆请求 GET登陆界面
/// - {prefix}/logout POST,GET退出
/// - {prefix}/register POST 注册账户
/// - {prefix}/token POST 登陆请求，获得token
/// - {prefix}/refresh POST 刷新token
pub fn get_route<S>(prefix: &str) -> Router<S>
where
    S: Sync + Send + Clone + 'static,
{
    let f = move |p: &str| -> String { format!("{}{p}", prefix) };
    Router::new()
        .route(&f("/login"), post(login).get(login_get))
        .route(&f("/register"), post(register))
        .route(&f("/logout"), post(logout).get(logout))
        .route(&f("/refresh"), post(token))
        .route(&f("/token"), post(token))
        .route(&f("/callback"), get(callback))
        .route(&f("/auth/gitee"), get(auth_gitee))
        .route(&f("/auth/gitee/callback"), get(auth_gitee_callback))
}

/// 登陆界面，渲染templete中的/auth/login.html
async fn login_get(
    Extension(state): Extension<AppState>,
    Query(query): Query<HashMap<String, String>>,
    CurrentUser(user): CurrentUser<Option<LoginUser>>,
) -> AppResult<Response> {
    if user.is_some() {
        let next = query.get("next").map(String::as_str).unwrap_or("/");
        return Ok(Redirect::to(next).into_response());
    }
    let context = state.tera_context();
    state
        .render_template("auth/login.html", &context)
        .map(IntoResponse::into_response)
}

#[derive(Debug, Deserialize, Validate)]
struct LoginInfo {
    #[validate(length(min = 3))]
    username: String,
    #[validate(length(min = 6))]
    password: String,
}

/// 处理登陆请求
async fn login(
    Query(query): Query<HashMap<String, String>>,
    Extension(state): Extension<AppState>,
    ClientIp(ip): ClientIp,
    headers: HeaderMap,
    mut auth: AuthContext,
    Json(info): Json<LoginInfo>,
) -> AppResult<Response> {
    let user = Security::load_user_by_pawwsord(&state, &info.username, &info.password).await?;
    let msg = "Incorrect username or password";
    let user_agent = headers
        .get(header::USER_AGENT)
        .and_then(|v| v.to_str().ok())
        .unwrap_or("unknown");
    let len = user_agent.len().min(255);
    let mut log = sys_login_log::ActiveModel {
        id: sea_orm::ActiveValue::NotSet,
        username: Set(info.username),
        result: Set(0),
        user_ip: Set(ip.map(|v| v.to_string()).unwrap_or("unknown".to_owned())),
        user_agent: Set(user_agent[0..len].to_owned()),
        created_at: Set(utils::current_datetime()),
    };
    if let Some((user, verity)) = user {
        if verity {
            log.insert(state.db()).await?;
            auth.login(&user);
            let next = query.get("next").map(String::as_str).unwrap_or("/");
            let mut res = Redirect::to(next).into_response();
            *res.status_mut() = StatusCode::FOUND;
            return Ok(res);
        }
    }
    log.result = Set(1);
    log.insert(state.db()).await?;
    let mut context = state.tera_context();
    context.insert("msg", msg);
    state
        .render_template("auth/login.html", &context)
        .map(IntoResponse::into_response)
}

/// 退出登陆并返回登陆界面
async fn logout(
    CurrentUser(_): CurrentUser<LoginUser>,
    mut auth: AuthContext,
) -> Result<Redirect, Response> {
    auth.logout();
    Ok(Redirect::to("/auth/login"))
}

async fn callback(
    Extension(state): Extension<AppState>,
    Extension(config): Extension<JwtConfig>,
    // clamis: Claims,
) -> Result<Json<serde_json::Value>, Response> {
    let redis = state.cache();
    let s: String = redis.get("key").await.unwrap();
    // println!("-- {}", s);
    Err((StatusCode::BAD_REQUEST, "Incorrect username or password").into_response())
}

async fn auth_gitee(Extension(state): Extension<AppState>) -> Result<Redirect, Response> {
    let redis = state.cache();
    let s: String = redis.get("key").await.unwrap();
    // println!("-- {}", s);
    Ok(Redirect::temporary("https://gitee.com/oauth/authorize?client_id=f6f446156a0cfadc1bf14586f3301a546516882de8e107bc1d75bbe88c5b53a5&redirect_uri=http://0.0.0.0:3001/auth3/callback&response_type=code"))
    // Redirect::temporary(uri).into_response()
    // Err((StatusCode::BAD_REQUEST, "Incorrect username or password").into_response())
}

async fn auth_gitee_callback(Extension(state): Extension<AppState>) -> Result<Redirect, Response> {
    let redis = state.cache();
    let s: String = redis.get("key").await.unwrap();
    // println!("-- {}", s);
    Ok(Redirect::temporary("https://gitee.com/oauth/authorize?client_id=f6f446156a0cfadc1bf14586f3301a546516882de8e107bc1d75bbe88c5b53a5&redirect_uri=http://0.0.0.0:3001/auth/callback&response_type=code"))
    // Redirect::temporary(uri).into_response()
    // Err((StatusCode::BAD_REQUEST, "Incorrect username or password").into_response())
}

#[derive(Debug, Deserialize, Validate)]
struct RegisterInfo {
    #[validate(length(min = 6))]
    username: String,
    nickname: String,
    #[validate(length(min = 6))]
    password: String,
}

async fn register(
    Extension(state): Extension<AppState>,
    Extension(config): Extension<JwtConfig>,
    Json(info): Json<RegisterInfo>,
) -> Result<impl IntoResponse, Response> {
    let db = state.db();
    if ane_system::dao::User::check_name(db, &info.username)
        .await
        .map_err(default_db_error)?
    {
        return Err((
            StatusCode::BAD_REQUEST,
            format!("User '{}' exists", info.username),
        )
            .into_response());
    }
    ane_system::Mutation::new_user(db, 1, info.username, info.nickname, &info.password)
        .await
        .unwrap();
    return Ok(Json("Ok"));
}

async fn token(
    Query(query): Query<HashMap<String, String>>,
    Extension(state): Extension<AppState>,
    ClientIp(ip): ClientIp,
    headers: HeaderMap,
    mut auth: AuthContext,
    Json(info): Json<LoginInfo>,
) -> AppResult<Response> {
    todo!()
}

async fn refresh(
    Query(query): Query<HashMap<String, String>>,
    Extension(state): Extension<AppState>,
    ClientIp(ip): ClientIp,
    headers: HeaderMap,
    mut auth: AuthContext,
    Json(info): Json<LoginInfo>,
) -> AppResult<Response> {
    todo!()
}
